﻿using System;
using System.Collections.Generic;
using System.Linq;
using Baik.Interface.Framework;
using Baik.Interface.Token;

namespace Baik.Token
{
    internal class Tokenizer : ITokenizer
    {
        public Tokenizer()
        {
            FileName = "REPL";
        }
        public string FileName { get; set; }
        public IQue<IToken> Tokenise(string code)
        {
            
            ICodeFile cf = new CodeFile(code);
            int lineNumber = 0;
            
            IFileLine fl = new FileLine(cf, lineNumber);
            var que = new Que<char>();
            var ca = code.ToCharArray();
            var cn = -1;
            que.Push(ca);
            Que<IToken> tokens = new  Que<IToken>();
            var ms = new TokenSwitcher();
            Token current = null;
            while (!que.Empty)
            {
                cn++;
                 if(que.Peek() == '\n') fl = new FileLine(cf, ++lineNumber);
                if (current == null)
                {
                    current = ms.Get(que.Peek());
                    current.Location = new Location(fl, cn);
                }
                if (current.Valid(que.Peek()))
                {
                    current.Add(que.Pop());
                }
                else
                {
                    if (current.Last == '.' && current.Type != TokenType.DotToken)
                    {
                        que.Push(current.Pop());
                        cn--;
                    }
                    if (current is Token.IdentToken)
                    {
                        tokens.Push((current as Token.IdentToken).SwitchToken());
                    }
                    else if(current.Type != TokenType.WhiteSpace)
                    {
                        tokens.Push(current);    
                    }
                    current = ms.Get(que.Peek());
                    current.Location = new Location(fl, cn);
                    //ignore white space note: all junk is whitespace
                    while (current.Type == TokenType.WhiteSpace)
                    {
                        que.Pop();
                        current = ms.Get(que.Peek());
                    }
                }
                
            }
            //the char number is not tracked
            if (current.Type != TokenType.WhiteSpace) tokens.Push(current);
            return tokens;
        }
        public class Que<T> : IQue<T>
        {
            private List<T> lst = new List<T>();

            public void Push(IEnumerable<T> items)
            {
                lst.AddRange(items);
            }

            public void Push(T item)
            {
                lst.Add(item);
            }

            public T Pop()
            {
                var rtn = Peek();
                lst.RemoveAt(0);
                return rtn;
            }

            public T Peek()
            {
                return lst[0];
            }

            public bool Empty
            {
                get { return lst.Count < 1; }
            }

            public void Clear()
            {
                lst.Clear();
            }
        }
        public class MethodSwitch<TInput, TOutput>
        {
            private List<Record> records = new List<Record>();
            private class Record
            {
                public Func<TInput, bool> Test { get; set; }
                public Func<TOutput> Create { get; set; }
            }
            public void Add(Func<TInput, bool> t, Func<TOutput> c)
            {
                records.Add(new Record() { Test = t, Create = c });
            }
            public TOutput Get(TInput value)
            {
                return records.First(r => r.Test(value)).Create();
                
            }
        }
        public class TokenSwitcher : MethodSwitch<char, Token>
        {
            public TokenSwitcher()
            {
                Add(Token.CommaToken.IsValid, Token.CommaToken.Create);
                Add(Token.DotToken.IsValid, Token.DotToken.Create);
                Add(Token.ColonToken.IsValid, Token.ColonToken.Create);
                Add(Token.SemiColonToken.IsValid, Token.SemiColonToken.Create);
                Add(Token.IdentToken.IsValid, Token.IdentToken.Create);
                Add(Token.NumericToken.IsValid, Token.NumericToken.Create);
                Add(Token.StringToken.IsValid, Token.StringToken.Create);
                Add(Token.OpenParenToken.IsValid, Token.OpenParenToken.Create);
                Add(Token.CommentToken.IsValid, Token.CommaToken.Create);
                Add(Token.OpenCurlyToken.IsValid, Token.OpenCurlyToken.Create);
                Add(Token.CloseCurlyToken.IsValid, Token.CloseCurlyToken.Create);
                Add(Token.CloseParenToken.IsValid, Token.CloseParenToken.Create);
                Add(Token.OpenSquareToken.IsValid, Token.OpenSquareToken.Create);
                Add(Token.CloseSquareToken.IsValid, Token.CloseSquareToken.Create);
                Add(Token.WhiteSpaceToken.IsValid, Token.WhiteSpaceToken.Create);
            }
        }

        public class CodeFile : ICodeFile
        {
            public CodeFile(string text)
            {
                Text = text;
            }

            public string FileName { get; set; }
            private string Text { get; set; }
            public string this[int lineNumber]
            {
                get { return Text.Split('\n')[lineNumber]; }
            }

        }
        public class FileLine : IFileLine
        {
            public FileLine(ICodeFile file, int line)
            {
                File = file;
                Line = line;
            }

            public ICodeFile File { get; set; }
            public int Line { get; set; }
            public string Value {
                get { return File[Line]; }
            }
        }

        public class Location : ILocation
        {
            public Location(IFileLine line, int c)
            {
                Line = line;
                Char = c;
            }

            public IFileLine Line { get; set; }
            public int Char { get; set; }
        }
        public abstract class Token : IToken
        {

            public virtual TokenType Type
            {
                get { return TokenType.WhiteSpace; }

            }
            public abstract bool Valid(char c);
            protected List<char> ProtectedValue = new List<char>();
            public virtual string Value { get { return new string(ProtectedValue.ToArray()); } }
            public ILocation Location { get; set; }

            public virtual void Add(char c)
            {
                ProtectedValue.Add(c);
            }
            public class StringToken : Token, IInternalToken
            {

                public static Token Create()
                {
                    return new StringToken();
                }
                public override TokenType Type
                {
                    get { return TokenType.String; }
                }
                private bool _escaped = false;
                private bool _completed = false;
                private char _initial = ' ';
                public static bool IsValid(char c)
                {
                    var lst = new List<char>("'\"".ToCharArray());
                    return lst.Contains(c);
                }
                public override void Add(char c)
                {

                    if (!_escaped && c == _initial) _completed = true;
                    _escaped = c == '\\';
                    if (!_completed || _initial != ' ') base.Add(c);
                    if (_initial == ' ') _initial = c;
                }
                public override bool Valid(char c)
                {
                    return !_completed;
                }

            }
            public class NumericToken : Token, IInternalToken
            {
                private static bool IsValidNumeric(char c)
                {
                    var lst = new List<char>(new char[] { '.', 'e' });
                    return lst.Contains(c) || IsNumber(c);
                }
                private static bool IsNumber(char c)
                {
                    var lst = new List<char>("1234567890".ToCharArray());
                    return lst.Contains(c);
                }
                public static bool IsValid(char c)
                {
                    return IsNumber(c);
                }
                public override TokenType Type
                {
                    get { return TokenType.Number; }
                }

                public override bool Valid(char c)
                {
                    return IsValidNumeric(c);
                }
                public static Token Create()
                {
                    return new NumericToken();
                }
            }
            public class OpenParenToken : Token, IInternalToken
            {
                public static bool IsValid(char c)
                {
                    return c == '(';
                }

                public override bool Valid(char c)
                {
                    return ProtectedValue.Count < 1 && IsValid(c);
                }
                public override TokenType Type
                {
                    get { return TokenType.OpenParen; }
                }

                public static Token Create()
                {
                    return new OpenParenToken();
                }
            }
            public class CloseParenToken : Token, IInternalToken
            {
                public static bool IsValid(char c)
                {
                    return c == ')';
                }

                public override bool Valid(char c)
                {
                    return ProtectedValue.Count < 1 && IsValid(c);
                }
                public override TokenType Type
                {
                    get { return TokenType.CloseParen; }
                }


                public static Token Create()
                {
                    return new CloseParenToken();
                }
            }
            public class SemiColonToken : Token, IInternalToken
            {
                public static bool IsValid(char c)
                {
                    return c == ';';
                }

                public override bool Valid(char c)
                {
                    return ProtectedValue.Count < 1 && IsValid(c);
                }
                public override TokenType Type
                {
                    get { return TokenType.Semicolon; }
                }



                public static Token Create()
                {
                    return new SemiColonToken();
                }
            }
            public class OpenCurlyToken : Token, IInternalToken
            {
                public static bool IsValid(char c)
                {
                    return c == '{';
                }

                public override bool Valid(char c)
                {
                    return ProtectedValue.Count < 1 && IsValid(c);
                }
                public override TokenType Type
                {
                    get { return TokenType.OpenCurly; }
                }



                public static Token Create()
                {
                    return new OpenCurlyToken();
                }
            }
            public class CloseCurlyToken : Token, IInternalToken
            {
                public static bool IsValid(char c)
                {
                    return c == '}';
                }

                public override bool Valid(char c)
                {
                    return ProtectedValue.Count < 1 && IsValid(c);
                }
                public override TokenType Type
                {
                    get { return TokenType.CloseCurly; }
                }
                public static Token Create()
                {
                    return new CloseCurlyToken();
                }
            }
            public class OpenSquareToken : Token, IInternalToken
            {
                public static bool IsValid(char c)
                {
                    return c == '[';
                }

                public override bool Valid(char c)
                {
                    return ProtectedValue.Count < 1 && IsValid(c);
                }
                public override TokenType Type
                {
                    get { return TokenType.OpenSquare; }
                }
                public static Token Create()
                {
                    return new OpenSquareToken();
                }
            }
            public class CloseSquareToken : Token, IInternalToken
            {
                public static bool IsValid(char c)
                {
                    return c == ']';
                }
                public override bool Valid(char c)
                {
                    return ProtectedValue.Count < 1 && IsValid(c);
                }
                public override TokenType Type
                {
                    get { return TokenType.CloseSquare; }
                }
                public static Token Create()
                {
                    return new CloseSquareToken();
                }
            }
            public class IdentToken : Token, IInternalToken
            {

                private static bool IsValidIdentInital(char c)
                {
                    var lst = new List<char>("abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray());
                    return lst.Contains(c);
                }
                private static bool IsValidIdentSubsiquen(char c)
                {
                    return NumericToken.IsValid(c) || IsValidIdentInital(c);
                }
                public static bool IsValid(char c)
                {
                    return IsValidIdentInital(c);
                }
                public override bool Valid(char c)
                {
                    return IsValidIdentSubsiquen(c);
                }
                public override TokenType Type
                {
                    get { return TokenType.Ident; }
                }
                public static Token Create()
                {
                    return new IdentToken();
                }
                public interface ISubToken
                {
                    bool IsValid(string value);
                }

                public IToken SwitchToken()
                {
                    var lst = new List<ISubToken>(new ISubToken[] { new ReturnToken(), new TrueToken(), new FalseToken(), new UndefinedToken() });
                    var rtn = lst.FirstOrDefault(itm => itm.IsValid(Value));
                    return (IToken)rtn ?? this;
                }
                public class ReturnToken : Token, ISubToken
                {
                    public override TokenType Type
                    {
                        get { return TokenType.Return;  }
                    }
                    
                    public override bool Valid(char c)
                    {
                        return false;
                    }
                    
                    public bool IsValid(string value)
                    {
                        return value == "return";
                    }
                }
                public class TrueToken : Token, ISubToken
                {
                    public override TokenType Type
                    {
                        get { return TokenType.True; }
                    }
                    public override bool Valid(char c)
                    {
                        return false;
                    }
                    public bool IsValid(string value)
                    {
                        return value == "true";
                    }
                }
                public class FalseToken : Token, ISubToken
                {
                    public override bool Valid(char c)
                    {
                        return false;
                    }
                    public override TokenType Type
                    {
                        get { return TokenType.False; }
                    }
                    public bool IsValid(string value)
                    {
                        return value == "false";
                    }
                }
                public class UndefinedToken : Token, ISubToken
                {
                    public override TokenType Type
                    {
                        get { return TokenType.Undefined;  }
                    }
                    public override bool Valid(char c)
                    {
                        return false;
                    }
                    public bool IsValid(string value)
                    {
                        return value == "undef" || value == "undefined";
                    }
                }
            }
            public class CommaToken : Token, IInternalToken
            {
                public static bool IsValid(char c)
                {
                    return c == ',';
                }

                public override bool Valid(char c)
                {
                    return ProtectedValue.Count < 1 && IsValid(c);
                }
                public override TokenType Type
                {
                    get { return TokenType.Comma; }
                }
                public static Token Create()
                {
                    return new CommaToken();

                }
            }
            public class DotToken : Token, IInternalToken
            {
                public static bool IsValid(char c)
                {
                    return c == '.';
                }

                public override bool Valid(char c)
                {
                    return ProtectedValue.Count < 1 && IsValid(c);
                }
                public override TokenType Type
                {
                    get { return TokenType.DotToken; }
                }
                public static Token Create()
                {
                    return new DotToken();

                }
            }
            public class ColonToken : Token, IInternalToken
            {
                public static bool IsValid(char c)
                {
                    return c == ':';
                }

                public override bool Valid(char c)
                {
                    return ProtectedValue.Count < 1 && IsValid(c);
                }
                public override TokenType Type
                {
                    get { return TokenType.Colon; }
                }
                public static Token Create()
                {
                    return new ColonToken();

                }
            }
            public class SymbolToken : Token, IInternalToken
            {
                private static bool IsSymbol(char c)
                {
                    var lst = new List<char>("~!@#$%^&*_+`-=:?>.<,|".ToCharArray());
                    return lst.Contains(c);
                }
                public static bool IsValid(char c)
                {
                    return IsSymbol(c);
                }
                public override TokenType Type
                {
                    get { return TokenType.Symbol; }
                }
                public override bool Valid(char c)
                {
                    return IsValid(c);
                }
                public static Token Create()
                {
                    return new SymbolToken();
                }
            }
            public class CommentToken : Token, IInternalToken
            {
                public static bool IsValid(char c)
                {
                    return c == '#';
                }
                public override TokenType Type
                {
                    get { return TokenType.Comment; }
                }
                public override bool Valid(char c)
                {
                    return c != '\n';
                }
                public static Token Create()
                {
                    return new CommentToken();
                }
            }
            public class WhiteSpaceToken : Token, IInternalToken
            {

                public static bool IsValid(char c)
                {
                    return true;
                }
                public override TokenType Type
                {
                    get { return TokenType.WhiteSpace; }
                }
                public override bool Valid(char c)
                {
                    return ProtectedValue.Count < 1;
                }
                public static Token Create()
                {
                    return new WhiteSpaceToken();
                }
            }

            public char Last
            {
                get
                {
                    return ProtectedValue.Count < 1 ? ' ' : ProtectedValue.Last();
                }
            }
            public char Pop()
            {
                var rtn = Last;
                ProtectedValue.RemoveAt(ProtectedValue.Count - 1);
                return rtn;
            }
        }
        internal interface IInternalToken : IToken
        {
            bool Valid(char c);
            void Add(char c);
        }
    }
}
